#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <math.h>

#include "chip8.h"

#define STATUS_BAR_HEIGHT 24
#define WINDOW_HEIGHT (CHIP8_SCR_HEIGHT * CHIP8_SCR_SCALE + STATUS_BAR_HEIGHT)

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

/// @brief 根据屏幕缓冲区的数据更新屏幕的回调函数
/// @param 屏幕缓冲区数据
void    update_screen       (BYTE [CHIP8_SCR_WIDTH][CHIP8_SCR_HEIGHT]);

/// @brief 清理SDL资源
/// 释放SDL创建的窗口、渲染器和纹理等资源
/// @note 在程序结束时调用以避免内存泄漏
void    sdl_cleanup         ();
int     sdl_init            (int, int);
void    sdl_audio_callback  (void*, Uint8*, int);
void    render_window       (int);
void    render_text         (const char*, int, int, SDL_Color);
void    print_instruction   (const WORD, const WORD, const char*);
void    play_sound          ();

SDL_Window*     g_window      = NULL;
SDL_Renderer*   g_renderer    = NULL;
SDL_Texture*    g_texture     = NULL;
TTF_Font*       g_font        = NULL;

uint8_t g_keymap[16] = {
    SDLK_x,
    SDLK_1,
    SDLK_2,
    SDLK_3,
    SDLK_q,
    SDLK_w,
    SDLK_e,
    SDLK_a,
    SDLK_s,
    SDLK_d,
    SDLK_z,
    SDLK_c,
    SDLK_4,
    SDLK_r,
    SDLK_f,
    SDLK_v,
};

SDL_Rect g_chip8_screen_rect = {
    0, 
    0, 
    CHIP8_SCR_WIDTH * CHIP8_SCR_SCALE, 
    CHIP8_SCR_HEIGHT * CHIP8_SCR_SCALE
};

BOOL g_render_asm = FALSE;

int main(int argc, char* argv[]) {

    const char* rom_file_arg = argc == 2 ? argv[1] : NULL;
    const char* render_asm_arg = argc == 3 ? argv[2] : NULL;
    //const char* render_asm_arg = "--render-asm";
    //const char* render_asm_arg = NULL;

    //const char* rom_file_arg = "/home/daxnet/Code/chip8-sharp/roms/programs/Clock Program [Bill Fisher, 1981].ch8";
    // const char* rom_file_arg = "/home/daxnet/Code/chip8-sharp/roms/games/Space Invaders [David Winter] (alt).ch8";
    //const char* rom_file_arg = "/home/daxnet/Code/chip8-sharp/roms/programs/IBM Logo.ch8";

    if (rom_file_arg == NULL) {
        fprintf(stderr, "Usage: %s <ROM file>\n", argv[0]);
        return -1;
    }
    
    int win_width = CHIP8_SCR_WIDTH * CHIP8_SCR_SCALE;
    if (render_asm_arg != NULL && strcmp(render_asm_arg, "--render-asm") == 0) {
        g_render_asm = TRUE;
    }

    if (sdl_init(win_width, WINDOW_HEIGHT) != RESULT_OK) {
        printf("Failed to initialize SDL\n");
        return RESULT_FAIL;
    }

    render_window(win_width);

    render_text("CHIP-8 EMULATOR. Copyright (C) 2025 by daxnet, https://gitee.com/daxnet/chip8-c", 
        5, 
        CHIP8_SCR_HEIGHT * CHIP8_SCR_SCALE + 5, 
        (SDL_Color){255, 255, 255, 255});

    int quit = 0;
    SDL_Event event;

    int init_result = chip8_init(rom_file_arg, update_screen, play_sound, print_instruction);
    if (init_result == RESULT_FAIL) {
        printf("Failed to initialize CHIP-8 emulator\n");
        sdl_cleanup();
        return RESULT_FAIL;
    }
    
    while (!quit) {
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                quit = 1;
            }

            if (event.type == SDL_KEYDOWN) {
                for (int i = 0; i < 16; i++) {
                    if (event.key.keysym.sym == g_keymap[i]) {
                        g_keys[i] = 1; // Set key state to pressed
                        break;
                    }
                }
            } 
            
            if (event.type == SDL_KEYUP) {
                for (int i = 0; i < 16; i++) {
                    if (event.key.keysym.sym == g_keymap[i]) {
                        g_keys[i] = 0; // Set key state to released
                        break;
                    }
                }
            }
        }

        // Run CPU cycle
        chip8_cycle();
        usleep(100); // Small delay to prevent excessive CPU usage
    }

    sdl_cleanup();
    return RESULT_OK;
}

void sdl_audio_callback(void* userdata, Uint8* stream, int len) {
    static double phase = 0;
    Sint16* audio_buffer = (Sint16*)stream;
    int samples = len / sizeof(Sint16);
    
    // Generate a 440Hz sine wave (beep sound)
    for (int i = 0; i < samples; i++) {
        audio_buffer[i] = (Sint16)(32767 * 0.1 * sin(phase));
        phase += 2.0 * M_PI * 440.0 / 44100.0; // 440Hz frequency, 44100Hz sample rate
        if (phase > 2.0 * M_PI) phase -= 2.0 * M_PI;
    }
}

void update_screen(BYTE screen[CHIP8_SCR_WIDTH][CHIP8_SCR_HEIGHT]) {
    // Update the screen using SDL_Texture for much better performance
    void* pixels;
    int pitch;
    
    // Lock the texture to get access to pixel data
    if (SDL_LockTexture(g_texture, NULL, &pixels, &pitch) < 0) {
        printf("Failed to lock texture: %s\n", SDL_GetError());
        return;
    }
    
    Uint32* pixel_buffer = (Uint32*)pixels;
    
    // Convert CHIP-8 screen buffer to RGB pixels
    for (int y = 0; y < CHIP8_SCR_HEIGHT; y++) {
        for (int x = 0; x < CHIP8_SCR_WIDTH; x++) {
            // Calculate pixel index
            int pixel_index = y * (pitch / sizeof(Uint32)) + x;
            
            // Set pixel color: white (0xFFFFFF) for on, black (0x000000) for off
            pixel_buffer[pixel_index] = screen[x][y] ? 0xFFFFFFFF : 0x00000000;
        }
    }
    
    // Unlock the texture
    SDL_UnlockTexture(g_texture);
    
    SDL_RenderCopy(g_renderer, g_texture, NULL, &g_chip8_screen_rect);
    SDL_RenderPresent(g_renderer);
}

void play_sound() {
    static SDL_AudioDeviceID audio_device = 0;
    static int audio_initialized = 0;
    
    if (!audio_initialized) {
        SDL_AudioSpec desired, obtained;
        desired.freq = 44100;
        desired.format = AUDIO_S16SYS;
        desired.channels = 1;
        desired.samples = 1024;
        desired.callback = sdl_audio_callback;
        desired.userdata = NULL;
        
        audio_device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
        if (audio_device == 0) {
            printf("Failed to open audio device: %s\n", SDL_GetError());
            return;
        }
        audio_initialized = 1;
    }
    
    // Play beep for 50ms
    SDL_PauseAudioDevice(audio_device, 0);
    SDL_Delay(50);
    SDL_PauseAudioDevice(audio_device, 1);
}

void render_window(int ww) {
    // Render the status bar at the bottom of the window
    SDL_SetRenderDrawColor(g_renderer, 0, 0, 255, 255); // Blue color
    SDL_Rect status_bar_rect = {
        0, 
        CHIP8_SCR_HEIGHT * CHIP8_SCR_SCALE,
        ww, 
        STATUS_BAR_HEIGHT
    };
    SDL_RenderFillRect(g_renderer, &status_bar_rect);

    SDL_RenderPresent(g_renderer);
}

void render_text(const char* text, int x, int y, SDL_Color color) {
    if (g_font == NULL) {
        return;
    }

    SDL_Surface* surface = TTF_RenderText_Solid(g_font, text, color);
    if (!surface) {
        printf("Failed to create text surface: %s\n", TTF_GetError());
        return;
    }

    SDL_Texture* texture = SDL_CreateTextureFromSurface(g_renderer, surface);
    if (!texture) {
        printf("Failed to create texture from surface: %s\n", SDL_GetError());
        SDL_FreeSurface(surface);
        return;
    }

    SDL_Rect dst_rect = {x, y, surface->w, surface->h};
    SDL_RenderCopy(g_renderer, texture, NULL, &dst_rect);

    SDL_DestroyTexture(texture);
    SDL_FreeSurface(surface);
}

void print_instruction(const WORD pc, const WORD ins, const char *inst_rep)
{
    if (g_render_asm) {
        printf("0x%04X: 0x%04X (%s)\n", pc, ins, inst_rep);
    }
}

void sdl_cleanup() {
    if (g_texture) {
        SDL_DestroyTexture(g_texture);
        g_texture = NULL;
    }
    if (g_renderer) {
        SDL_DestroyRenderer(g_renderer);
        g_renderer = NULL;
    }
    if (g_window) {
        SDL_DestroyWindow(g_window);
        g_window = NULL;
    }
    SDL_Quit();
}

int sdl_init(int ww, int wh) {
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
        printf("SDL Initialize failed: %s\n", SDL_GetError());
        return RESULT_FAIL;
    }

    if (TTF_Init() == -1) {
        printf("TTF_Init failed: %s\n", TTF_GetError());
        SDL_Quit();
        return RESULT_FAIL;
    }

    g_window = SDL_CreateWindow(
        "CHIP8 Emulator",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        ww, // Scale the width
        wh, // Scale the height
        SDL_WINDOW_SHOWN
    );

    if (!g_window) {
        printf("Failed to create Window instance: %s\n", SDL_GetError());
        sdl_cleanup();
        return RESULT_FAIL;
    }

    g_renderer = SDL_CreateRenderer(g_window, -1, SDL_RENDERER_ACCELERATED);
    if (!g_renderer) {
        printf("Renderer created failed: %s\n", SDL_GetError());
        sdl_cleanup();
        return RESULT_FAIL;
    }

    // Create texture for efficient pixel rendering
    g_texture = SDL_CreateTexture(g_renderer, 
                                  SDL_PIXELFORMAT_ARGB8888, 
                                  SDL_TEXTUREACCESS_STREAMING, 
                                  CHIP8_SCR_WIDTH, 
                                  CHIP8_SCR_HEIGHT);
    if (!g_texture) {
        printf("Texture creation failed: %s\n", SDL_GetError());
        sdl_cleanup();
        return RESULT_FAIL;
    }

    g_font = TTF_OpenFont("./arial.ttf", 14);  // Load from current directory
    if (!g_font) {
        printf("Failed to load font: %s\n", TTF_GetError());
        g_font = NULL;
    }

    SDL_SetRenderDrawColor(g_renderer, 0, 0, 0, 255); // Set background color to black
    SDL_RenderClear(g_renderer);

    return RESULT_OK;
}